/**
 * 
 */
package cn.bblocks.cacheTest.configuration;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import javax.servlet.http.HttpServletRequest;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;


import com.google.common.base.Strings;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.mysql.jdbc.StringUtils;

import cn.bblocks.cacheTest.constant.Constant;
import cn.bblocks.cacheTest.exceptions.BusinessException;
import lombok.extern.slf4j.Slf4j;

/** 
* @version:v1.0
* @Description: 防止重复提交(只是基于地址的(参数未涉及)，所以无关的接口尽量不要防重复提交)
* @author: jzhao
* @date: 2019年9月10日下午7:29:36
*/
@Aspect
@Component
@Slf4j
public class RepeatSubmitAspect {
	
//	 	@Autowired
//	    private RedisLock redisLock;
	
	private static final Cache<String, Object> CACHES = CacheBuilder.newBuilder()
			// 最大缓存 20000 个
			.maximumSize(20000)
			// 设置写缓存后 5 秒钟过期
			.expireAfterWrite(5, TimeUnit.SECONDS).build();
	
    private SpelExpressionParser spelParser = new SpelExpressionParser();

	
	    //@Autowired
	    //RedisTemplate redisTemplate;

	    @Pointcut("@annotation(noRepeatSubmit)")
	    public void pointCut(NoRepeatSubmit noRepeatSubmit) {
	    }

	    @Around("pointCut(noRepeatSubmit)")
	    public Object around(ProceedingJoinPoint joinPoint, NoRepeatSubmit noRepeatSubmit) throws Throwable {
	        int lockms = noRepeatSubmit.lockTime();

	       

			 // 解析SpEL表达式获取结果
			String key = getKey(joinPoint, noRepeatSubmit);
			try {
				if (!StringUtils.isNullOrEmpty(key)) {
					if (CACHES.getIfPresent(key) != null) {
						throw new BusinessException(Constant.ErrorCode.ERR_REPEAT,"请勿重复请求");
					}
					// 如果是第一次请求,就将 key 当前对象压入缓存中
					CACHES.put(key, key);
				}
				
				return joinPoint.proceed();
			} catch (Throwable throwable) {
				throw throwable;
			} finally {
				// TODO 为了演示效果,这里就不调用 CACHES.invalidate(key); 代码了
				if (!StringUtils.isNullOrEmpty(key)) 
					CACHES.invalidate(key);
			}
	        
	        

	    }

	    private String getKey(String token, String path) {
	        return "lock_repeat::"+token + path;
	    }
	    
	    /**
		 * key 的生成策略,如果想灵活可以写成接口与实现类的方式（TODO 后续讲解）
		 *
		 * @param keyExpress 表达式
		 * @param args       参数
		 * @return 生成的key
		 */
		private String getKey(ProceedingJoinPoint joinPoint, NoRepeatSubmit noRepeatSubmit) {
			if(StringUtils.isNullOrEmpty(noRepeatSubmit.key()))
				return null;
			
			
			  MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
		      Method method = methodSignature.getMethod();
		      List<String> paramNameList = Arrays.asList(methodSignature.getParameterNames());
		      List<Object> paramList = Arrays.asList(joinPoint.getArgs());
		      methodSignature.getDeclaringType();
		   
		        
			//将方法的参数名和参数值一一对应的放入上下文中
	        EvaluationContext ctx = new StandardEvaluationContext();
	        for (int i = 0; i < paramNameList.size(); i++) {
	            ctx.setVariable(paramNameList.get(i), paramList.get(i));
	        }
	        
	        
	        // 解析SpEL表达式获取结果
	        String value = spelParser.parseExpression(noRepeatSubmit.key()).getValue(ctx).toString();
	       
			return methodSignature.getDeclaringTypeName().substring(methodSignature.getDeclaringTypeName().lastIndexOf(".")+1)+"::"+method.getName()+"::"+value;
		}
	

}
